using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline;
using Microsoft.Xna.Framework.Content.Pipeline.Graphics;
using Microsoft.Xna.Framework.Content.Pipeline.Processors;

// TODO: replace these with the processor input and output types.
using TerrainContent;
using TInput = Microsoft.Xna.Framework.Content.Pipeline.Graphics.Texture2DContent;
using TOutput = TerrainImporters.TerrainContent;

namespace TerrainImporters
{
    [ContentProcessor(DisplayName = "TerrainImporters.TerrainProcessor")]
    public class TerrainProcessor : ContentProcessor<TInput, TOutput>
    {

        private int _meshTesselation = 10;
        [DisplayName("Mesh Tesselation Factor")]
        [DefaultValue(10)]
        public int MeshTesselation
        {
            get { return _meshTesselation; }
            set { _meshTesselation = value; }
        }

        private float _mergePercentage = .15f;
        [DisplayName("Merge Percentage")]
        [DefaultValue(.15f)]
        public float MergePercentage
        {
            get { return _mergePercentage; }
            set { _mergePercentage = value; }
        }

        [DisplayName("Merge Slop")]
        [DefaultValue(.05f)]
        public float MergeSlop
        {
            get { return _mergeSlop; }
            set { _mergeSlop = value; }
        }

        private float _mergeSlop;
        private int _levels;
        private PixelBitmapContent<Vector2> ctnt;
        public override TOutput Process(TInput input, ContentProcessorContext context)
        {

            input.ConvertBitmapType(typeof(PixelBitmapContent<Vector2>));
            ctnt = (PixelBitmapContent<Vector2>)input.Mipmaps[0];


            TerrainDataQuadtreeNode rootnode = new TerrainDataQuadtreeNode() {Area = new Rectangle() {X = 0, Y = 0, Width = ctnt.Width, Height = ctnt.Height}};

            _levels = 9;
            RecursveSplit(rootnode, 0);

            Texture2DContent outctnt = (Texture2DContent) context.Convert<Texture2DContent, TextureContent>(input, "TextureProcessor",
                                                                                                            new OpaqueDataDictionary {{"GenerateMipmaps", true}});
            var ranges = from range in Enumerable.Range(0, _levels).Select(element => (float) Math.Pow(2, element))
                         let mergeStart = range*(1f - MergePercentage)
                         let MergeEnd = range - MergeSlop
                         select new LODInfo {Range = range, MergeStart = mergeStart, MergeEnd = MergeEnd };

            outctnt.ConvertBitmapType(typeof(PixelBitmapContent<Vector2>));
            return new TerrainContent()
                       {
                           MaxLOD = _levels,
                           RootNode = rootnode,
                           Texture = outctnt,
                           FullMesh =
                               context.Convert<MeshContent, ModelContent>(MakePlane(_meshTesselation, input.Identity),
                                                                          "EffectModelProcessor",
                                                                          new OpaqueDataDictionary
                                                                              {{"Shader", "Terrain.fx"}}),
                           QuarterMesh =
                               context.Convert<MeshContent, ModelContent>(MakePlane(_meshTesselation/2, input.Identity),
                                                                          "EffectModelProcessor",
                                                                          new OpaqueDataDictionary
                                                                              {{"Shader", "Terrain.fx"}}),
                           LODData = ranges.ToArray()
                       };
        }

        private MeshContent MakePlane(int tesselationFactor, ContentIdentity ident)
        {
            MeshBuilder blder = MeshBuilder.StartMesh("NodeMesh");

            Vector2[] UVCoords = new Vector2[(tesselationFactor + 1) * (tesselationFactor + 1)];

            // Create the terrain vertices.
            for (int y = 0; y <= tesselationFactor; y++)
            {
                for (int x = 0; x <= tesselationFactor; x++)
                {
                    Vector3 position;

                    position.X = (x/(float)tesselationFactor);
                    position.Z = (y/(float)tesselationFactor);

                    position.Y = 0;

                    UVCoords[x + y * (tesselationFactor + 1)] = new Vector2((x / (float)tesselationFactor), (y / (float)tesselationFactor));
                    blder.CreatePosition(position);
                }
            }


            // Create a vertex channel for holding texture coordinates.
            int texCoordId = blder.CreateVertexChannel<Vector2>(
                VertexChannelNames.TextureCoordinate(0));

            // Create the individual triangles that make up our terrain.
            for (int y = 0; y < tesselationFactor; y++)
            {
                for (int x = 0; x < tesselationFactor; x++)
                {
                    AddVertex(blder, x, y, texCoordId, tesselationFactor, UVCoords);
                    AddVertex(blder, x + 1, y, texCoordId, tesselationFactor, UVCoords);
                    AddVertex(blder, x + 1, y + 1, texCoordId, tesselationFactor, UVCoords);

                    AddVertex(blder, x, y, texCoordId, tesselationFactor, UVCoords);
                    AddVertex(blder, x + 1, y + 1, texCoordId, tesselationFactor, UVCoords);
                    AddVertex(blder, x, y + 1, texCoordId, tesselationFactor, UVCoords);
                }
            }
            var finMesh= blder.FinishMesh();
            finMesh.Identity = ident;
            return finMesh;
        }

        private void AddVertex(MeshBuilder blder, int x, int y, int UVID, int tesselationFactor, Vector2[] UVCoords)
        {
            blder.AddTriangleVertex(x + y * (tesselationFactor + 1));
            blder.SetVertexChannelData(UVID, new Vector2(x,y) * 1/(float)_meshTesselation);
        }
        private int GetHeight(Vector2 imageVal)
        {
            return (int) ((imageVal.Y*256 + imageVal.X) - 1000);
        }

        private void GetMinMax(Rectangle area, PixelBitmapContent<Vector2> bitmap, out int min, out int max)
        {
            min = Int32.MaxValue;
            max = 0;
            int pixelVal = 0;
            for (int i = 0; i < area.Width; i++)
                for (int j = 0; j < area.Height; j++)
                {
                    pixelVal = GetHeight(bitmap.GetPixel(i+area.X, j+area.Y));

                    if (pixelVal == -1000)
                        pixelVal = 0;
                    if (pixelVal > max)
                        max = pixelVal;
                    if (pixelVal < min)
                        min = pixelVal;
                }
        }
        private TerrainDataQuadtreeNode NewNode(TerrainDataQuadtreeNode parent, int X, int Y, int LOD)
        {
            var node = new TerrainDataQuadtreeNode() { Area = new Rectangle() { X = parent.Area.X + X, Y = parent.Area.Y + Y, Width = parent.Area.Width / 2, Height = parent.Area.Height / 2 }, LOD = LOD };
            GetMinMax(new Rectangle(parent.Area.X+X, parent.Area.Y+Y, parent.Area.Width / 2, parent.Area.Height / 2), ctnt, out node.MinAlt, out node.MaxAlt);
            return node;
        }
        private void RecursveSplit(TerrainDataQuadtreeNode node, int level)
        {
            if (level < _levels)
            {
                node.Children = new TerrainDataQuadtreeNode[4];
                node.Children[0] = NewNode(node, 0, 0, level);
                node.Children[1] = NewNode(node, node.Area.Width/2, 0, level);
                node.Children[2] = NewNode(node, 0, node.Area.Height/2, level);
                node.Children[3] = NewNode(node, node.Area.Width / 2, node.Area.Height / 2, level);
                for (int i = 0; i < 4; i++)
                    RecursveSplit(node.Children[i], level + 1);
            }
        }
    }

    [ContentProcessor(DisplayName = "Custom Effect Model Processor")]
    public class EffectModelProcessor : ModelProcessor
    {
        private string _shader;

        [DisplayName("Shader File")]
        public string Shader
        {
            get { return _shader; }
            set { _shader = value; }
        }

        protected override MaterialContent ConvertMaterial(MaterialContent material, ContentProcessorContext context)
        {
            EffectMaterialContent newMaterial = new EffectMaterialContent();

            string effectPath = Path.GetFullPath(_shader);

            newMaterial.Effect = new ExternalReference<EffectContent>(effectPath);

            return base.ConvertMaterial(newMaterial, context);
        }

        public override ModelContent Process(NodeContent input, ContentProcessorContext context)
        {
            //blder.SetMaterial(material);))
            return base.Process(input, context);
        }
    }

    [ContentSerializerRuntimeType("TerrainContent.TerrainData, TerrainContent")]
    public class TerrainContent
    {
        public TerrainDataQuadtreeNode RootNode;
        public int MaxLOD;
        public Texture2DContent Texture;
        public ModelContent FullMesh;
        public ModelContent QuarterMesh;
        public LODInfo[] LODData;
    }

    [ContentSerializerRuntimeType("TerrainContent.TerrainQuadtreeNode, TerrainContent")]
    public class TerrainDataQuadtreeNode
    {
        public TerrainDataQuadtreeNode Parent;
        public TerrainDataQuadtreeNode[] Children;
        public Rectangle Area;
        public int MinAlt;
        public int MaxAlt;
        public int LOD;
    }

}